﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.Input;

namespace ParserGenerator.SyntacticAnalysis
{
    partial class Identifier
    {
        public static Identifier New(IEnumerable<string> items, SourceContext context = null)
        {
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            string name = string.Join(".", items);
            Identifier id = New(name, context);
            id.CheckDottedName();
            return id;
        }

        public static Identifier New(IEnumerable<Identifier> items, SourceContext context = null)
        {
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            if (context == null && items.All(x => x.Context != null))
            {
                SourceLocation begin = default(SourceLocation);
                SourceLocation end = default(SourceLocation);
                bool first = true;
                foreach (Identifier item in items)
                {
                    if (first)
                    {
                        first = false;
                        begin = item.Context.Begin;
                    }
                    end = item.Context.End;
                }
                if (!first)
                    context = SourceContext.New(begin, end);
            }
            string name = string.Join(".", from x in items select x.Name);
            Identifier id = New(name, context);
            id.CheckDottedName();
            return id;
        }

        private static Regex _lowerSnakeCasePattern = new Regex("^(?<leading>_*)(?<word>[a-z][a-z0-9]*)(?:_(?<word>[a-z][a-z0-9]*))+(?<trailing>_*)$");
        private static Regex _upperSnakeCasePattern = new Regex("^(?<leading>_*)(?<word>[A-Z][A-Z0-9]*)(?:_(?<word>[A-Z][A-Z0-9]*))+(?<trailing>_*)$");
        private static Regex _lowerDashCasePattern = new Regex("^(?<word>[a-z][a-z0-9]*)(?:-(?<word>[a-z][a-z0-9]*))+(?<trailing>[?!*]|'+)?$");
        private static Regex _upperDashCasePattern = new Regex("^(?<word>[A-Z][A-Z0-9]*)(?:-(?<word>[A-Z][A-Z0-9]*))+(?<trailing>[?!*]|'+)?$");
        private static Regex _lowerCamelCasePattern = new Regex("^(?<leading>_*)(?<word>[a-z][a-z0-9]*)(?<word>[A-Z][a-z0-9]*)*(?<trailing>_*)$");
        private static Regex _upperCamelCasePattern = new Regex("^(?<leading>_*)(?<word>[A-Z][a-z0-9]*)(?<word>[A-Z][a-z0-9]*)*(?<trailing>_*)$");
        private static Regex _dottedNamePattern = new Regex("^(?<item>[A-Za-z_][A-Za-z0-9_]*)(?:\\.(?<item>[A-Za-z_][A-Za-z0-9_]*))*$");

        public bool IsLowerSnakeCase => _lowerSnakeCasePattern.IsMatch(Name);
        public bool IsUpperSnakeCase => _upperSnakeCasePattern.IsMatch(Name);
        public bool IsLowerDashCase => _lowerDashCasePattern.IsMatch(Name);
        public bool IsUpperDashCase => _upperDashCasePattern.IsMatch(Name);
        public bool IsLowerCamelCase => _lowerCamelCasePattern.IsMatch(Name);
        public bool IsUpperCamelCase => _upperCamelCasePattern.IsMatch(Name);
        public bool IsDottedName => _dottedNamePattern.IsMatch(Name);

        public bool IsSnakeCase => IsLowerSnakeCase || IsUpperSnakeCase;
        public bool IsDashCase => IsLowerDashCase || IsUpperDashCase;
        public bool IsCamelCase => IsLowerCamelCase || IsUpperCamelCase;

        public bool IsLowerCase => IsLowerSnakeCase || IsLowerDashCase || IsLowerCamelCase;
        public bool IsUpperCase => IsUpperSnakeCase || IsUpperDashCase || IsUpperCamelCase;

        public IdentifierCaseStyle CaseStyle
        {
            get
            {
                if (IsLowerSnakeCase)
                    return IdentifierCaseStyle.LowerSnakeCase;
                if (IsUpperSnakeCase)
                    return IdentifierCaseStyle.UpperSnakeCase;
                if (IsLowerDashCase)
                    return IdentifierCaseStyle.LowerDashCase;
                if (IsUpperDashCase)
                    return IdentifierCaseStyle.UpperDashCase;
                if (IsLowerCamelCase)
                    return IdentifierCaseStyle.LowerCamelCase;
                if (IsUpperCamelCase)
                    return IdentifierCaseStyle.UpperCamelCase;
                return IdentifierCaseStyle.Unknown;
            }
        }

        public IEnumerable<string> Words
        {
            get
            {
                Match match = _lowerSnakeCasePattern.Match(Name);
                if (!match.Success)
                    match = _upperSnakeCasePattern.Match(Name);
                if (!match.Success)
                    match = _lowerDashCasePattern.Match(Name);
                if (!match.Success)
                    match = _upperDashCasePattern.Match(Name);
                if (!match.Success)
                    match = _lowerCamelCasePattern.Match(Name);
                if (!match.Success)
                    match = _upperCamelCasePattern.Match(Name);
                if (!match.Success)
                {
                    string message = string.Format(SR.IdentifierCaseStyleUnknownNoWords, Name.AsSingleQuote());
                    NameErrorException e = new NameErrorException(message);
                    e.AddLocation(Context);
                    throw e;
                }
                Group group = match.Groups["word"];
                foreach (Capture item in group.Captures)
                    yield return item.Value;
            }
        }

        public IEnumerable<Identifier> Items
        {
            get
            {
                Match match = _dottedNamePattern.Match(Name);
                if (!match.Success)
                {
                    string message = string.Format(SR.IdentifierNotDottedNameNoItems, Name.AsSingleQuote());
                    NameErrorException e = new NameErrorException(message);
                    e.AddLocation(Context);
                    throw e;
                }
                Group group = match.Groups["item"];
                foreach (Capture item in group.Captures)
                {
                    if (Context == null)
                        yield return New(item.Value);
                    else
                    {
                        SourceLocation begin = Offset(Context.Begin, item.Index);
                        SourceLocation end = Offset(Context.Begin, item.Index + item.Length);
                        SourceContext context = SourceContext.New(begin, end);
                        yield return New(item.Value, context);
                    }
                }
            }
        }

        private static SourceLocation Offset(SourceLocation loc, int i)
        {
            int index = loc.Index + i;
            int offset = loc.Offset + i;
            int column = loc.Column + i;
            return new SourceLocation(loc.Document, index, loc.Line, offset, column);
        }

        private static string ToUpperCamel(string word)
        {
            if (word == null)
                throw new ArgumentNullException(nameof(word));
            switch (word)
            {
                case "io":
                    return "IO";
            }
            return char.ToUpperInvariant(word[0]) + word.Substring(1);
        }

        private static string ToLowerCamel(string word)
        {
            if (word == null)
                throw new ArgumentNullException(nameof(word));
            return char.ToLowerInvariant(word[0]) + word.Substring(1);
        }

        public Identifier ToUpperCase()
        {
            if (IsUpperCase)
                return this;
            if (IsLowerSnakeCase || IsLowerDashCase)
                return New(Name.ToUpperInvariant(), Context);
            Match match = _lowerCamelCasePattern.Match(Name);
            if (!match.Success)
            {
                string message = string.Format(SR.IdentifierCaseStyleUnknownNoUpperCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
            StringBuilder sb = new StringBuilder();
            sb.Append(match.Groups["leading"].Value);
            Group group = match.Groups["word"];
            foreach (Capture item in group.Captures)
                sb.Append(ToUpperCamel(item.Value));
            sb.Append(match.Groups["trailing"].Value);
            string name = sb.ToString();
            return New(name, Context);
        }

        public Identifier ToLowerCase()
        {
            if (IsLowerCase)
                return this;
            if (IsUpperSnakeCase || IsUpperDashCase)
                return New(Name.ToUpperInvariant(), Context);
            Match match = _upperCamelCasePattern.Match(Name);
            if (!match.Success)
            {
                string message = string.Format(SR.IdentifierCaseStyleUnknownNoLowerCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
            StringBuilder sb = new StringBuilder();
            sb.Append(match.Groups["leading"].Value);
            Group group = match.Groups["word"];
            bool first = true;
            foreach (Capture item in group.Captures)
            {
                if (!first)
                {
                    sb.Append(item.Value);
                    continue;
                }
                sb.Append(ToLowerCamel(item.Value));
                first = false;
            }
            sb.Append(match.Groups["trailing"].Value);
            string name = sb.ToString();
            return New(name, Context);
        }

        public void CheckLowerSnakeCase()
        {
            if (!IsLowerSnakeCase)
            {
                string message = string.Format(SR.IdentifierNotLowerSnakeCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckUpperSnakeCase()
        {
            if (!IsUpperSnakeCase)
            {
                string message = string.Format(SR.IdentifierNotUpperSnakeCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckLowerDashCase()
        {
            if (!IsLowerDashCase)
            {
                string message = string.Format(SR.IdentifierNotLowerDashCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckUpperDashCase()
        {
            if (!IsUpperDashCase)
            {
                string message = string.Format(SR.IdentifierNotUpperDashCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckLowerCamelCase()
        {
            if (!IsLowerCamelCase)
            {
                string message = string.Format(SR.IdentifierNotLowerCamelCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckUpperCamelCase()
        {
            if (!IsUpperCamelCase)
            {
                string message = string.Format(SR.IdentifierNotUpperCamelCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckDottedName()
        {
            if (!IsDottedName)
            {
                string message = string.Format(SR.IdentifierNotDottedName, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckSnakeCase()
        {
            if (!IsSnakeCase)
            {
                string message = string.Format(SR.IdentifierNotSnakeCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckDashCase()
        {
            if (!IsDashCase)
            {
                string message = string.Format(SR.IdentifierNotDashCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckCamelCase()
        {
            if (!IsCamelCase)
            {
                string message = string.Format(SR.IdentifierNotCamelCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckLowerCase()
        {
            if (!IsLowerCase)
            {
                string message = string.Format(SR.IdentifierNotLowerCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }

        public void CheckUpperCase()
        {
            if (!IsUpperCase)
            {
                string message = string.Format(SR.IdentifierNotUpperCase, Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(Context);
                throw e;
            }
        }
    }
}
